package com.pivotal.cloud.security.password;

import java.util.HashMap;
import java.util.Map;

/**
 * @className: DelegatePasswordEncoder
 * @projectName: PivotalCloud项目
 * @module: PivotalCloud项目-DelegatePasswordEncoder类，主要位于Security安全模块-Delegate授权/委派密码加密模块
 * @content: DelegatePasswordEncoder-Delegate授权/委派密码加密
 * @author: Powered by Marklin
 * @datetime: 2025-06-24 13:41
 * @version: 1.0.0
 * @copyright: Copyright © 2018-2025 PivotalCloud Systems Incorporated. All rights
 * reserved.
 */
public class DelegatePasswordEncoder implements PasswordEncoder {

	private static final String DEFAULT_ID_PREFIX = "{";

	private static final String DEFAULT_ID_SUFFIX = "}";

	private static final String NO_PASSWORD_ENCODER_MAPPED = "There is no password encoder mapped for the id '%s'. "
			+ "Check your configuration to ensure it matches one of the registered encoders.";

	private static final String NO_PASSWORD_ENCODER_PREFIX = "Given that there is no default password encoder configured, each password must have a password encoding prefix. "
			+ "Please either prefix this password with '{noop}' or set a default password encoder in `DelegatingPasswordEncoder`.";

	private static final String MALFORMED_PASSWORD_ENCODER_PREFIX = "The name of the password encoder is improperly "
			+ "formatted or incomplete. The format should be '%sENCODER%spassword'.";

	private final String idPrefix;

	private final String idSuffix;

	private final String idForEncode;

	private final PasswordEncoder passwordEncoderForEncode;

	private final Map<String, PasswordEncoder> idToPasswordEncoder;

	private PasswordEncoder defaultPasswordEncoderForMatches = new UnmappedIdPasswordEncoder();

	public DelegatePasswordEncoder(String idForEncode, Map<String, PasswordEncoder> idToPasswordEncoder) {
		this(idForEncode, idToPasswordEncoder, DEFAULT_ID_PREFIX, DEFAULT_ID_SUFFIX);
	}

	public DelegatePasswordEncoder(String idForEncode, Map<String, PasswordEncoder> idToPasswordEncoder,
			String idPrefix, String idSuffix) {
		if (idForEncode == null) {
			throw new IllegalArgumentException("idForEncode cannot be null");
		}
		if (idPrefix == null) {
			throw new IllegalArgumentException("prefix cannot be null");
		}
		if (idSuffix == null || idSuffix.isEmpty()) {
			throw new IllegalArgumentException("suffix cannot be empty");
		}
		if (idPrefix.contains(idSuffix)) {
			throw new IllegalArgumentException("idPrefix " + idPrefix + " cannot contain idSuffix " + idSuffix);
		}

		if (!idToPasswordEncoder.containsKey(idForEncode)) {
			throw new IllegalArgumentException(
					"idForEncode " + idForEncode + "is not found in idToPasswordEncoder " + idToPasswordEncoder);
		}
		for (String id : idToPasswordEncoder.keySet()) {
			if (id == null) {
				continue;
			}
			if (!idPrefix.isEmpty() && id.contains(idPrefix)) {
				throw new IllegalArgumentException("id " + id + " cannot contain " + idPrefix);
			}
			if (id.contains(idSuffix)) {
				throw new IllegalArgumentException("id " + id + " cannot contain " + idSuffix);
			}
		}
		this.idForEncode = idForEncode;
		this.passwordEncoderForEncode = idToPasswordEncoder.get(idForEncode);
		this.idToPasswordEncoder = new HashMap<>(idToPasswordEncoder);
		this.idPrefix = idPrefix;
		this.idSuffix = idSuffix;
	}

	public void setDefaultPasswordEncoderForMatches(PasswordEncoder defaultPasswordEncoderForMatches) {
		if (defaultPasswordEncoderForMatches == null) {
			throw new IllegalArgumentException("defaultPasswordEncoderForMatches cannot be null");
		}
		this.defaultPasswordEncoderForMatches = defaultPasswordEncoderForMatches;
	}

	/**
	 * 密码加密
	 * @param rawPassword 原始密码
	 * @return 返回结果
	 */
	@Override
	public String encode(CharSequence rawPassword) {
		return this.idPrefix + this.idForEncode + this.idSuffix + this.passwordEncoderForEncode.encode(rawPassword);
	}

	/**
	 * 验证密码
	 * @param rawPassword 原始密码
	 * @param encodedPassword 加密密码
	 * @return 返回结果
	 */
	@Override
	public boolean matches(CharSequence rawPassword, String encodedPassword) {
		if (rawPassword == null && encodedPassword == null) {
			return true;
		}
		String id = extractId(encodedPassword);
		PasswordEncoder delegate = this.idToPasswordEncoder.get(id);
		if (delegate == null) {
			return this.defaultPasswordEncoderForMatches.matches(rawPassword, encodedPassword);
		}
		String password = extractEncodedPassword(encodedPassword);
		return delegate.matches(rawPassword, password);
	}

	/**
	 * 更新密码编码
	 * @param encodedPassword 加密密码
	 * @return 返回结果
	 */
	@Override
	public boolean upgradeEncoding(String encodedPassword) {
		String id = extractId(encodedPassword);
		if (!this.idForEncode.equalsIgnoreCase(id)) {
			return true;
		}
		else {
			String password = extractEncodedPassword(encodedPassword);
			return this.idToPasswordEncoder.get(id).upgradeEncoding(password);
		}
	}

	private String extractId(String prefixEncodedPassword) {
		if (prefixEncodedPassword == null) {
			return null;
		}
		int start = prefixEncodedPassword.indexOf(this.idPrefix);
		if (start != 0) {
			return null;
		}
		int end = prefixEncodedPassword.indexOf(this.idSuffix, start);
		if (end < 0) {
			return null;
		}
		return prefixEncodedPassword.substring(start + this.idPrefix.length(), end);
	}

	private String extractEncodedPassword(String prefixEncodedPassword) {
		int start = prefixEncodedPassword.indexOf(this.idSuffix);
		return prefixEncodedPassword.substring(start + this.idSuffix.length());
	}

	private class UnmappedIdPasswordEncoder implements PasswordEncoder {

		@Override
		public String encode(CharSequence rawPassword) {
			throw new UnsupportedOperationException("encode is not supported");
		}

		@Override
		public boolean matches(CharSequence rawPassword, String prefixEncodedPassword) {
			String id = extractId(prefixEncodedPassword);
			if (id != null && !id.isEmpty()) {
				throw new IllegalArgumentException(String.format(NO_PASSWORD_ENCODER_MAPPED, id));
			}
			if (prefixEncodedPassword != null && !prefixEncodedPassword.isEmpty()) {
				int start = prefixEncodedPassword.indexOf(DelegatePasswordEncoder.this.idPrefix);
				int end = prefixEncodedPassword.indexOf(DelegatePasswordEncoder.this.idSuffix, start);
				if (start < 0 && end < 0) {
					throw new IllegalArgumentException(NO_PASSWORD_ENCODER_PREFIX);
				}
			}
			throw new IllegalArgumentException(String.format(MALFORMED_PASSWORD_ENCODER_PREFIX,
					DelegatePasswordEncoder.this.idPrefix, DelegatePasswordEncoder.this.idSuffix));
		}

	}

}
